在 Android 應用開發中,架構設計是讓應用更具可擴展性、易於測試和維護的關鍵。而 MVP(Model-View-Presenter)架構是一種常用的設計模式,能夠清晰地將業務邏輯與視圖分離。在這篇文章中,我將介紹 MVP 架構的基本概念,並展示一個使用 MVP 架構開發的 Android 登入範例應用程式。
MVP 是一種將應用邏輯拆分為三個主要部分的架構模式:
這裡我們將實現一個簡單的登入功能,當用戶輸入帳號和密碼後,應用會檢查是否匹配,並根據結果給出不同的提示。該應用基於 MVP 架構進行設計,我們將展示三個主要組件:Model、View 和 Presenter。
MainActivity
是應用的主要界面,實現了 MainContract.view
介面,用於顯示登入的相關信息。
布局檔:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.main.MainActivity">
<TextView
android:id="@+id/account_tv"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center"
android:text="帳號 :"
app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintHorizontal_bias="0.431"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="@+id/guideline3"
app:layout_constraintVertical_bias="0.297" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.26" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.1" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.29" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.38" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.47058824" />
<TextView
android:id="@+id/password_tv"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center"
android:text="密碼 :"
app:layout_constraintBottom_toTopOf="@+id/guideline5"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="@+id/guideline4" />
<EditText
android:id="@+id/account_et"
android:layout_width="0dp"
android:layout_height="0dp"
android:ems="10"
android:hint="輸入帳號"
android:inputType="text"
app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toTopOf="@+id/guideline3" />
<EditText
android:id="@+id/password_et"
android:layout_width="0dp"
android:layout_height="0dp"
android:ems="10"
android:hint="輸入密碼"
android:inputType="text"
app:layout_constraintBottom_toTopOf="@+id/guideline5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toTopOf="@+id/guideline4" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="Button"
app:layout_constraintBottom_toTopOf="@+id/guideline9"
app:layout_constraintEnd_toStartOf="@+id/guideline7"
app:layout_constraintHorizontal_bias="0.547"
app:layout_constraintStart_toStartOf="@+id/guideline8"
app:layout_constraintTop_toTopOf="@+id/guideline6"
app:layout_constraintVertical_bias="0.531"
android:onClick="setButton"/>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.60328317" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.6885645" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.3187348" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline9"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.7564979" />
</androidx.constraintlayout.widget.ConstraintLayout>
java檔:
public class MainActivity extends AppCompatActivity implements MainContract.view {
private EditText account, password; // 帳號和密碼輸入框
private Button sent; // 登入按鈕
private MainPresenter presenter; // Presenter 對象
private Context context = this; // 上下文對象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 設定佈局文件
// 初始化視圖元素
account = findViewById(R.id.account_et);
password = findViewById(R.id.password_et);
sent = findViewById(R.id.button);
// 創建 Presenter 實例
presenter = new MainPresenter(this);
}
// 按鈕點擊事件,啟動登入流程
public void setButton(View view) {
presenter.getLoginData(account.getText().toString(), password.getText().toString());
}
// 顯示登入錯誤訊息
@Override
public void loginError() {
Toast.makeText(context, "帳密錯誤", Toast.LENGTH_SHORT).show();
}
// 顯示使用者名稱
@Override
public void showUserName(String userName) {
Toast.makeText(context, "歡迎登入 " + userName, Toast.LENGTH_SHORT).show();
}
}
在這裡,MainActivity
充當 View 層,負責與用戶的交互。當按下登入按鈕時,會將用戶的帳號和密碼傳遞給 Presenter
,由 Presenter
進行邏輯處理。
MainContract
定義了 View 和 Presenter 之間的契約,這裡我們定義了 View 和 Presenter 的互動方法。
public interface MainContract {
// View 層的接口
public interface view {
void showUserName(String userName); // 顯示用戶名稱
void loginError(); // 顯示登入錯誤訊息
}
// Presenter 層的接口
interface presenter {
void getLoginData(String account, String password); // 獲取登入數據
}
// Model 層的接口
interface modle {
String getUserName(); // 獲取用戶名稱
}
}
這是 MVP 架構中的一個重要部分,MainContract
確保了 View 和 Presenter 之間的互動是通過定義好的方法進行的,讓整個架構更加清晰。
MainPresenter
負責接收來自 View 的用戶輸入,並將其交給 Model 處理。處理完後再將結果返回給 View 進行展示。
public class MainPresenter implements MainContract.presenter {
private String Username;
private MainContract.view callBack; // View 層的引用
private MainModle modle; // Model 層的引用
public MainPresenter(MainContract.view view) {
this.callBack = view;
modle = new MainModle(this); // 建立 Model
}
// 檢查帳號和密碼的邏輯
@Override
public void getLoginData(String account, String password) {
if (account.equals("1111") && password.equals("0000")) {
Username = modle.getUserName(); // 從 Model 獲取用戶名
callBack.showUserName(Username); // 回傳給 View
} else {
callBack.loginError(); // 登入失敗
}
}
}
MainPresenter
實現了 MainContract.presenter
介面,它負責帳號和密碼的驗證邏輯,並與 MainModle
和 MainActivity
進行交互。
MainModle
負責處理數據邏輯,這裡簡單地返回一個用戶名稱。
public class MainModle implements MainContract.modle {
private MainContract.presenter callBack;
public MainModle(MainContract.presenter present) {
this.callBack = present;
}
// 返回使用者名稱
@Override
public String getUserName() {
return "Damn";
}
}MainModle
負責業務數據的處理,將處理結果返回給 Presenter
。
MVP 架構提供了將應用邏輯與 UI 分離的解決方案,這不僅讓代碼更加模組化,也有助於提升應用程式的可測試性。在這篇文章中,我們以一個簡單的登入功能為例,演示了如何實現 MVP 架構。透過這樣的設計,開發者可以更容易地進行維護和擴展。